package network

import (
	"errors"
	"fmt"
	"math/rand"
	"net"
	"operator/pkg/format"
	"operator/pkg/iptables"
	"operator/pkg/service"
	"operator/pkg/types/resource"
	"operator/pkg/util"
	"strconv"
	"strings"
	"sync"
)

type OutBoundServer struct {
	mu       sync.RWMutex
	listener *net.TCPListener
	version  string
	resource *map[resource.ResourceString][]resource.Resource
	service  service.Interface
	egress   *map[string]struct{}
}

func (ts *OutBoundServer) listen() {
	conn, err := ts.listener.Accept()
	if err != nil {
		format.Errors("pbk/network/outbound.go", 30, err.Error())
		return
	}

	go ts.dealConn(conn)
}

func (ts *OutBoundServer) dealConn(conn net.Conn) {
	buffer := make([]byte, 1024*30)
	for {
		n, err := conn.Read(buffer)
		if err != nil {
			fmt.Println(err)
			break
		}

		buf, err := ts.writeToRemote(conn, buffer[:n])
		if err != nil {
			//调用服务失败需要处理
		}

		_, err = conn.Write(buf)
		if err != nil {
			format.Errors("pbk/network/outbound.go", 54, err.Error())
			break
		}
	}
}

func (ts *OutBoundServer) writeToRemote(conn net.Conn, buf []byte) ([]byte, error) {
	var result []byte

	addr, _ := iptables.RawAddrFromConn(conn)
	format.Println("pbk/network/outbound.go", 64, addr.String())
	if _, ok := (*ts.egress)[addr.IP.String()]; ok {
		err := ts.service.Do(func() error {
			egressConn, err := net.Dial("tcp", addr.String())
			if err != nil {
				format.Errors("pbk/network/outbound.go", 68, err.Error())
				return err
			}
			_, err = egressConn.Write(buf)
			if err != nil {
				format.Errors("pbk/network/outbound.go", 73, err.Error())
				return err
			}
			cbuf := make([]byte, 1024*30)
			n, err := egressConn.Read(cbuf)
			if err != nil {
				format.Errors("pbk/network/outbound.go", 79, err.Error())
				return err
			}

			//result = make([]byte, n, n)
			result = cbuf[:n]

			return nil
		})

		return result, err
	}

	path := ts.getPathFromBuf(buf)
	if path == "" {
		format.Errors("pbk/network/outbound.go", 94, "error path")
		return nil, errors.New("error path")
	}

	r := ts.getResource(path)
	if r == nil {
		format.Errors("pbk/network/outbound.go", 100, "error resource")
		return nil, errors.New("error resource")
	}

	remote, err := ts.getConnFromResource(r)
	if err != nil {
		format.Errors("pbk/network/outbound.go", 106, err.Error())
		return nil, err
	}

	err = ts.service.Do(func() error {
		_, err = remote.Write(buf)
		if err != nil {
			format.Errors("pbk/network/outbound.go", 113, err.Error())
			return err
		}

		cbuf := make([]byte, 1024*30)
		n, err := remote.Read(cbuf)
		if err != nil {
			format.Errors("pbk/network/outbound.go", 120, err.Error())
			return err
		}

		//result = make([]byte, n, n)
		result = cbuf[:n]

		return nil
	})
	if err != nil {
		format.Errors("pbk/network/outbound.go", 130, err.Error())
		return nil, err
	}

	return result, nil
}

func (ts *OutBoundServer) error() []byte {
	str :=
		`HTTP1.1 500 

`

	return []byte(str)
}

func (ts *OutBoundServer) getPathFromBuf(buf []byte) string {
	str := string(buf)
	arr := strings.SplitN(str, "\r\n", 2)
	if len(arr) < 2 {
		return ""
	}

	arr = strings.Split(arr[0], " ")
	if len(arr) != 3 {
		return ""
	}

	path := arr[1]
	if strings.Contains(path, "?") {
		arr = strings.Split(path, "?")
		path = arr[0]
		if path[len(path)-1] == '/' {
			return path[:len(path)-1]
		}

		return path
	}

	return path
}

func (ts *OutBoundServer) getConnFromResource(r *resource.Resource) (net.Conn, error) {
	c, err := net.Dial("tcp", r.Host)
	if err != nil {
		format.Errors("pbk/network/outbound.go", 175, err.Error())
		return nil, err
	}

	return c, err
}

func (ts *OutBoundServer) getResource(path string) (r *resource.Resource) {
	rstr := resource.GenerateResourceString(ts.version, path)
	if lists, ok := (*ts.resource)[rstr]; ok {
		i := rand.Intn(len(lists))
		one := lists[i]
		return &one
	}

	return nil
}

func (ts *OutBoundServer) Run(stopCh <-chan struct{}) {
	for {
		select {
		case <-stopCh:
			break
		default:
			ts.listen()
		}
	}
}

func NewOutBoundServer(version string, resource *map[resource.ResourceString][]resource.Resource, egress *map[string]struct{}) (*OutBoundServer, error) {
	addr, err := net.ResolveTCPAddr("tcp", ":"+strconv.Itoa(util.SidecarOutBoundPort))
	if err != nil {
		format.Errors("pkg/network/outbound.go", 207, err.Error())
		return nil, err
	}
	listener, err := net.ListenTCP("tcp", addr)
	if err != nil {
		format.Errors("pkg/network/outbound.go", 212, err.Error())
		return nil, err
	}

	tcp := &OutBoundServer{
		listener: listener,
		mu:       sync.RWMutex{},
		version:  version,
		resource: resource,
		service:  service.NewHystrix(),
		egress:   egress,
	}

	return tcp, nil
}
